/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.forces.maneuvers;

import ore.ForceModel;
import ore.SpacecraftState;
import ore.errors.OrekitException;
import ore.propagation.events.DateDetector;
import ore.propagation.events.EventDetector;
import ore.propagation.numerical.TimeDerivativesEquations;
import ore.time.AbsoluteDate;

import org.apache.commons.math.geometry.Vector3D;

/** 
 * This class implements a simple maneuver with constant thrust.
 * 
 * The maneuver is defined by a direction in the satellite frame.  The
 * current attitude of the spacecraft, defined by the current
 * spacecraft state, will be used to compute the thrust direction in
 * inertial frame. A typical case for tangential maneuvers is to use a
 * {@link ore.attitudes.LofOffset LOF aligned} attitude law for state
 * propagation and a velocity increment along the +X satellite axis.
 * 
 * @author Fabien Maussion
 * @author V&eacute;ronique Pommier-Maurussane
 * @author Luc Maisonobe
 */
public class ConstantThrustManeuver
    extends Object
    implements ForceModel
{

    /** 
     * Reference gravity acceleration constant (m/s<sup>2</sup>).
     */
    public static final double G0 = 9.80665;


    /** Start of the maneuver.
     */
    public final AbsoluteDate startDate;

    /** End of the maneuver.
     */
    public final AbsoluteDate endDate;

    /** Engine thrust.
     */
    public final double thrust;

    /**
     * Engine fuel flow rate is a change in mass, calculated from
     * thrust and specific impulse.
     */
    public final double flowRate;

    /** Direction of the acceleration in satellite frame.
     */
    public final Vector3D direction;

    /** State of the engine.
     */
    private volatile boolean firing;



    /** 
     * Constant direction and thrust.
     * 
     * @param date Maneuver date
     * @param duration The duration of the thrust (s) (if negative,
     * the date is considered to be the stop date)
     * @param thrust The thrust force (N)
     * @param isp Engine specific impulse (s)
     * @param direction The acceleration direction in satellite frame.
     */
    public ConstantThrustManeuver(AbsoluteDate date, double duration,
                                  double thrust, double isp,
                                  Vector3D direction)
    {
        super();

        if (duration >= 0) {
            this.startDate = date;
            this.endDate   = new AbsoluteDate(date, duration);
        } else {
            this.endDate   = date;
            this.startDate = new AbsoluteDate(endDate, duration);
        }

        this.thrust    = thrust;
        this.flowRate  = -thrust / (G0 * isp);
        this.direction = direction.normalize();
    }


    /** {@inheritDoc}
     */
    public void addContribution(SpacecraftState s, TimeDerivativesEquations adder)
        throws OrekitException
    {
        if (this.firing) {
            /*
             * Compute thrust acceleration in inertial frame
             */
            adder.addAcceleration(new Vector3D((thrust / s.getMass()),
                                               s.getAttitude().getRotation().applyInverseTo(direction)),
                                  s.getFrame());
            /*
             * Compute flow rate
             */
            adder.addMassDerivative(this.flowRate);
        }
    }

    /** 
     * @return Date detector functions for start and end of maneuver
     * as engine firing
     */
    public EventDetector[] getEventsDetectors() {
        return new EventDetector[] {
            new FiringStartDetector(), new FiringStopDetector()
        };
    }


    /** 
     * Date Detector function for start of maneuver.
     */
    private class FiringStartDetector
        extends DateDetector
    {

        public FiringStartDetector() {
            super(startDate);
        }

        /** {@inheritDoc}
         */
        public int eventOccurred(SpacecraftState s, boolean increasing) {

            firing = true;
            return RESET_DERIVATIVES;
        }
    }

    /**
     * Date Detector function for end of maneuver.
     */
    private class FiringStopDetector 
        extends DateDetector
    {

        public FiringStopDetector() {
            super(endDate);
        }

        /** {@inheritDoc}
         */
        public int eventOccurred(SpacecraftState s, boolean increasing) {

            firing = false;

            return RESET_DERIVATIVES;
        }
    }
}
